#  Copyright (c) "Neo4j"
#  Neo4j Sweden AB [https://neo4j.com]
#  #
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#  #
#      https://www.apache.org/licenses/LICENSE-2.0
#  #
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
from __future__ import annotations

from typing import Any, Dict, List
from unittest.mock import MagicMock, patch

import pytest
from neo4j import Driver, Query
from neo4j_graphrag.schema import (
    BASE_ENTITY_LABEL,
    BASE_KG_BUILDER_LABEL,
    EXCLUDED_LABELS,
    EXCLUDED_RELS,
    INDEX_QUERY,
    LIST_LIMIT,
    NODE_PROPERTIES_QUERY,
    REL_PROPERTIES_QUERY,
    REL_QUERY,
    _value_sanitize,
    format_schema,
    get_enhanced_schema_cypher,
    get_schema,
    get_structured_schema,
)


def _query_return_value(*args: Any, **kwargs: Any) -> list[Any]:
    query = kwargs.get("query", args[1] if len(args) > 1 else "")
    if NODE_PROPERTIES_QUERY in query:
        return [
            {
                "output": {
                    "properties": [{"property": "property_a", "type": "STRING"}],
                    "label": "LabelA",
                }
            }
        ]
    if REL_PROPERTIES_QUERY in query:
        return [
            {
                "output": {
                    "type": "REL_TYPE",
                    "properties": [{"property": "rel_prop", "type": "STRING"}],
                }
            }
        ]
    if REL_QUERY in query:
        return [
            {"output": {"start": "LabelA", "type": "REL_TYPE", "end": "LabelB"}},
            {"output": {"start": "LabelA", "type": "REL_TYPE", "end": "LabelC"}},
        ]
    if "SHOW CONSTRAINTS" == query:
        return ["fake constraints"]
    if INDEX_QUERY == query:
        return ["fake indexes"]

    raise AssertionError("Unexpected query")


@patch("neo4j_graphrag.schema.query_database", side_effect=_query_return_value)
def test_get_schema_ensure_formatted_response(driver: Driver) -> None:
    result = get_schema(driver)
    assert (
        result
        == """Node properties:
LabelA {property_a: STRING}
Relationship properties:
REL_TYPE {rel_prop: STRING}
The relationships:
(:LabelA)-[:REL_TYPE]->(:LabelB)
(:LabelA)-[:REL_TYPE]->(:LabelC)"""
    )


def test_get_structured_schema_happy_path(driver: MagicMock) -> None:
    get_structured_schema(driver)
    assert 5 == driver.execute_query.call_count
    calls = driver.execute_query.call_args_list

    args, kwargs = calls[0]
    query_obj = args[0]
    assert isinstance(query_obj, Query)
    assert query_obj.text == NODE_PROPERTIES_QUERY
    assert query_obj.timeout is None
    assert kwargs["database_"] is None
    assert kwargs["parameters_"] == {
        "EXCLUDED_LABELS": EXCLUDED_LABELS + [BASE_ENTITY_LABEL, BASE_KG_BUILDER_LABEL]
    }

    args, kwargs = calls[1]
    query_obj = args[0]
    assert isinstance(query_obj, Query)
    assert query_obj.text == REL_PROPERTIES_QUERY
    assert query_obj.timeout is None
    assert kwargs["database_"] is None
    assert kwargs["parameters_"] == {"EXCLUDED_LABELS": EXCLUDED_RELS}

    args, kwargs = calls[2]
    query_obj = args[0]
    assert isinstance(query_obj, Query)
    assert query_obj.text == REL_QUERY
    assert query_obj.timeout is None
    assert kwargs["database_"] is None
    assert kwargs["parameters_"] == {
        "EXCLUDED_LABELS": EXCLUDED_LABELS + [BASE_ENTITY_LABEL, BASE_KG_BUILDER_LABEL]
    }

    args, kwargs = calls[3]
    query_obj = args[0]
    assert isinstance(query_obj, Query)
    assert query_obj.text == "SHOW CONSTRAINTS"
    assert query_obj.timeout is None
    assert kwargs["database_"] is None
    assert kwargs["parameters_"] == {}

    args, kwargs = calls[4]
    query_obj = args[0]
    assert isinstance(query_obj, Query)
    assert query_obj.text == INDEX_QUERY
    assert query_obj.timeout is None
    assert kwargs["database_"] is None
    assert kwargs["parameters_"] == {}


@patch("neo4j_graphrag.schema.query_database", side_effect=_query_return_value)
def test_get_schema_ensure_structured_response(driver: MagicMock) -> None:
    result = get_structured_schema(driver)

    assert result["node_props"]["LabelA"] == [
        {"property": "property_a", "type": "STRING"}
    ]
    assert result["rel_props"]["REL_TYPE"] == [
        {"property": "rel_prop", "type": "STRING"}
    ]
    assert result["relationships"] == [
        {"end": "LabelB", "start": "LabelA", "type": "REL_TYPE"},
        {"end": "LabelC", "start": "LabelA", "type": "REL_TYPE"},
    ]
    assert result["metadata"]["constraint"] == ["fake constraints"]
    assert result["metadata"]["index"] == ["fake indexes"]


@pytest.mark.parametrize(
    "description, input_value, expected_output",
    [
        (
            "Small list",
            {"key1": "value1", "small_list": list(range(15))},
            {"key1": "value1", "small_list": list(range(15))},
        ),
        (
            "Oversized list",
            {"key1": "value1", "oversized_list": list(range(LIST_LIMIT + 1))},
            {"key1": "value1"},
        ),
        (
            "Nested oversized list",
            {"key1": "value1", "oversized_list": {"key": list(range(150))}},
            {"key1": "value1", "oversized_list": {}},
        ),
        (
            "Dict in list",
            {
                "key1": "value1",
                "oversized_list": [1, 2, {"key": list(range(LIST_LIMIT + 1))}],
            },
            {"key1": "value1", "oversized_list": [1, 2, {}]},
        ),
        (
            "Dict in nested list",
            {
                "key1": "value1",
                "deeply_nested_lists": [
                    [[[{"final_nested_key": list(range(LIST_LIMIT + 1))}]]]
                ],
            },
            {"key1": "value1", "deeply_nested_lists": [[[[{}]]]]},
        ),
        (
            "Bare oversized list",
            list(range(LIST_LIMIT + 1)),
            None,
        ),
        (
            "None value",
            None,
            None,
        ),
    ],
)
def test__value_sanitize(
    description: str, input_value: Dict[str, Any], expected_output: Any
) -> None:
    """Test the _value_sanitize function."""
    assert (
        _value_sanitize(input_value) == expected_output
    ), f"Failed test case: {description}"


@pytest.mark.parametrize(
    "description, schema, is_enhanced, expected_output",
    [
        (
            "Enhanced, string property with high distinct count",
            {
                "node_props": {
                    "Person": [
                        {
                            "property": "name",
                            "type": "STRING",
                            "values": ["Alice", "Bob", "Charlie"],
                            "distinct_count": 11,
                        }
                    ]
                },
                "rel_props": {},
                "relationships": [],
            },
            True,
            (
                "Node properties:\n"
                "- **Person**\n"
                '  - `name`: STRING Example: "Alice"\n'
                "Relationship properties:\n"
                "\n"
                "The relationships:\n"
            ),
        ),
        (
            "Enhanced, string property with low distinct count",
            {
                "node_props": {
                    "Animal": [
                        {
                            "property": "species",
                            "type": "STRING",
                            "values": ["Cat", "Dog"],
                            "distinct_count": 2,
                        }
                    ]
                },
                "rel_props": {},
                "relationships": [],
            },
            True,
            (
                "Node properties:\n"
                "- **Animal**\n"
                "  - `species`: STRING Available options: ['Cat', 'Dog']\n"
                "Relationship properties:\n"
                "\n"
                "The relationships:\n"
            ),
        ),
        (
            "Enhanced, numeric property with min and max",
            {
                "node_props": {
                    "Person": [
                        {"property": "age", "type": "INTEGER", "min": 20, "max": 70}
                    ]
                },
                "rel_props": {},
                "relationships": [],
            },
            True,
            (
                "Node properties:\n"
                "- **Person**\n"
                "  - `age`: INTEGER Min: 20, Max: 70\n"
                "Relationship properties:\n"
                "\n"
                "The relationships:\n"
            ),
        ),
        (
            "Enhanced, numeric property with values",
            {
                "node_props": {
                    "Event": [
                        {
                            "property": "date",
                            "type": "DATE",
                            "values": ["2021-01-01", "2021-01-02"],
                        }
                    ]
                },
                "rel_props": {},
                "relationships": [],
            },
            True,
            (
                "Node properties:\n"
                "- **Event**\n"
                '  - `date`: DATE Example: "2021-01-01"\n'
                "Relationship properties:\n"
                "\n"
                "The relationships:\n"
            ),
        ),
        (
            "Enhanced, list property that should be skipped",
            {
                "node_props": {
                    "Document": [
                        {
                            "property": "embedding",
                            "type": "LIST",
                            "min_size": 150,
                            "max_size": 200,
                        }
                    ]
                },
                "rel_props": {},
                "relationships": [],
            },
            True,
            (
                "Node properties:\n"
                "- **Document**\n"
                "Relationship properties:\n"
                "\n"
                "The relationships:\n"
            ),
        ),
        (
            "Enhanced, list property that should be included",
            {
                "node_props": {
                    "Document": [
                        {
                            "property": "keywords",
                            "type": "LIST",
                            "min_size": 2,
                            "max_size": 5,
                        }
                    ]
                },
                "rel_props": {},
                "relationships": [],
            },
            True,
            (
                "Node properties:\n"
                "- **Document**\n"
                "  - `keywords`: LIST Min Size: 2, Max Size: 5\n"
                "Relationship properties:\n"
                "\n"
                "The relationships:\n"
            ),
        ),
        (
            "Enhanced, relationship string property with high distinct count",
            {
                "node_props": {},
                "rel_props": {
                    "KNOWS": [
                        {
                            "property": "since",
                            "type": "STRING",
                            "values": ["2000", "2001", "2002"],
                            "distinct_count": 15,
                        }
                    ]
                },
                "relationships": [],
            },
            True,
            (
                "Node properties:\n"
                "\n"
                "Relationship properties:\n"
                "- **KNOWS**\n"
                '  - `since`: STRING Example: "2000"\n'
                "The relationships:\n"
            ),
        ),
        (
            "Enhanced, relationship string property with low distinct count",
            {
                "node_props": {},
                "rel_props": {
                    "LIKES": [
                        {
                            "property": "intensity",
                            "type": "STRING",
                            "values": ["High", "Medium", "Low"],
                            "distinct_count": 3,
                        }
                    ]
                },
                "relationships": [],
            },
            True,
            (
                "Node properties:\n"
                "\n"
                "Relationship properties:\n"
                "- **LIKES**\n"
                "  - `intensity`: STRING Available options: ['High', 'Medium', 'Low']\n"
                "The relationships:\n"
            ),
        ),
        (
            "Enhanced, relationship numeric property with min and max",
            {
                "node_props": {},
                "rel_props": {
                    "WORKS_WITH": [
                        {
                            "property": "since",
                            "type": "INTEGER",
                            "min": 1995,
                            "max": 2020,
                        }
                    ]
                },
                "relationships": [],
            },
            True,
            (
                "Node properties:\n"
                "\n"
                "Relationship properties:\n"
                "- **WORKS_WITH**\n"
                "  - `since`: INTEGER Min: 1995, Max: 2020\n"
                "The relationships:\n"
            ),
        ),
        (
            "Enhanced, relationship list property that should be skipped",
            {
                "node_props": {},
                "rel_props": {
                    "KNOWS": [
                        {
                            "property": "embedding",
                            "type": "LIST",
                            "min_size": 150,
                            "max_size": 200,
                        }
                    ]
                },
                "relationships": [],
            },
            True,
            (
                "Node properties:\n"
                "\n"
                "Relationship properties:\n"
                "- **KNOWS**\n"
                "The relationships:\n"
            ),
        ),
        (
            "Enhanced, relationship list property that should be included",
            {
                "node_props": {},
                "rel_props": {
                    "KNOWS": [
                        {
                            "property": "messages",
                            "type": "LIST",
                            "min_size": 2,
                            "max_size": 5,
                        }
                    ]
                },
                "relationships": [],
            },
            True,
            (
                "Node properties:\n"
                "\n"
                "Relationship properties:\n"
                "- **KNOWS**\n"
                "  - `messages`: LIST Min Size: 2, Max Size: 5\n"
                "The relationships:\n"
            ),
        ),
        (
            "Enhanced, relationship numeric property without min and max",
            {
                "node_props": {},
                "rel_props": {
                    "OWES": [
                        {
                            "property": "amount",
                            "type": "FLOAT",
                            "values": [3.14, 2.71],
                        }
                    ]
                },
                "relationships": [],
            },
            True,
            (
                "Node properties:\n"
                "\n"
                "Relationship properties:\n"
                "- **OWES**\n"
                '  - `amount`: FLOAT Example: "3.14"\n'
                "The relationships:\n"
            ),
        ),
        (
            "Enhanced, property with empty values list",
            {
                "node_props": {
                    "Person": [
                        {
                            "property": "name",
                            "type": "STRING",
                            "values": [],
                            "distinct_count": 15,
                        }
                    ]
                },
                "rel_props": {},
                "relationships": [],
            },
            True,
            (
                "Node properties:\n"
                "- **Person**\n"
                "  - `name`: STRING \n"
                "Relationship properties:\n"
                "\n"
                "The relationships:\n"
            ),
        ),
        (
            "Enhanced, property with missing values",
            {
                "node_props": {
                    "Person": [
                        {
                            "property": "name",
                            "type": "STRING",
                            "distinct_count": 15,
                        }
                    ]
                },
                "rel_props": {},
                "relationships": [],
            },
            True,
            (
                "Node properties:\n"
                "- **Person**\n"
                "  - `name`: STRING \n"
                "Relationship properties:\n"
                "\n"
                "The relationships:\n"
            ),
        ),
    ],
)
def test_format_schema(
    description: str, schema: Dict[str, Any], is_enhanced: bool, expected_output: str
) -> None:
    result = format_schema(schema, is_enhanced)
    assert result == expected_output, f"Failed test case: {description}"


@pytest.mark.parametrize(
    "description, structured_schema, label_or_type, properties, exhaustive, sample_size, is_relationship, expected_query",
    [
        (
            "Exhaustive, string property",
            {"metadata": {"index": []}},
            "Person",
            [{"property": "name", "type": "STRING"}],
            True,
            5,
            False,
            (
                "MATCH (n:`Person`)\n"
                "WITH collect(distinct substring(toString(n.`name`), 0, 50)) AS `name_values`\n"
                "RETURN {`name`: {values: `name_values`[..10], distinct_count: size(`name_values`)}} AS output"
            ),
        ),
        (
            "Non-exhaustive, string property",
            {"metadata": {"index": []}},
            "Person",
            [{"property": "name", "type": "STRING"}],
            False,
            5,
            False,
            (
                "MATCH (n:`Person`) WITH n LIMIT 5\n"
                "WITH collect(distinct substring(toString(n.`name`), 0, 50)) AS `name_values`\n"
                "RETURN {`name`: {values: `name_values`}} AS output"
            ),
        ),
        (
            "Exhaustive, integer property",
            {"metadata": {"index": []}},
            "Person",
            [{"property": "age", "type": "INTEGER"}],
            True,
            5,
            False,
            (
                "MATCH (n:`Person`)\n"
                "WITH min(n.`age`) AS `age_min`,\n"
                "     max(n.`age`) AS `age_max`,\n"
                "     count(distinct n.`age`) AS `age_distinct`\n"
                "RETURN {`age`: {min: toString(`age_min`), max: toString(`age_max`), distinct_count: `age_distinct`}} AS output"
            ),
        ),
        (
            "Non-exhaustive, integer property",
            {"metadata": {"index": []}},
            "Person",
            [{"property": "age", "type": "INTEGER"}],
            False,
            5,
            False,
            (
                "MATCH (n:`Person`) WITH n LIMIT 5\n"
                "WITH collect(distinct toString(n.`age`)) AS `age_values`\n"
                "RETURN {`age`: {values: `age_values`}} AS output"
            ),
        ),
        (
            "Exhaustive, float property",
            {"metadata": {"index": []}},
            "Person",
            [{"property": "salary", "type": "FLOAT"}],
            True,
            5,
            False,
            (
                "MATCH (n:`Person`)\n"
                "WITH min(n.`salary`) AS `salary_min`,\n"
                "     max(n.`salary`) AS `salary_max`,\n"
                "     count(distinct n.`salary`) AS `salary_distinct`\n"
                "RETURN {`salary`: {min: toString(`salary_min`), max: toString(`salary_max`), distinct_count: `salary_distinct`}} AS output"
            ),
        ),
        (
            "Non-exhaustive, float property",
            {"metadata": {"index": []}},
            "Person",
            [{"property": "salary", "type": "FLOAT"}],
            False,
            5,
            False,
            (
                "MATCH (n:`Person`) WITH n LIMIT 5\n"
                "WITH collect(distinct toString(n.`salary`)) AS `salary_values`\n"
                "RETURN {`salary`: {values: `salary_values`}} AS output"
            ),
        ),
        (
            "Exhaustive, date property",
            {"metadata": {"index": []}},
            "Person",
            [{"property": "birth_date", "type": "DATE"}],
            True,
            5,
            False,
            (
                "MATCH (n:`Person`)\n"
                "WITH min(n.`birth_date`) AS `birth_date_min`,\n"
                "     max(n.`birth_date`) AS `birth_date_max`,\n"
                "     count(distinct n.`birth_date`) AS `birth_date_distinct`\n"
                "RETURN {`birth_date`: {min: toString(`birth_date_min`), max: toString(`birth_date_max`), distinct_count: `birth_date_distinct`}} AS output"
            ),
        ),
        (
            "Non-exhaustive, date property",
            {"metadata": {"index": []}},
            "Person",
            [{"property": "birth_date", "type": "DATE"}],
            False,
            5,
            False,
            (
                "MATCH (n:`Person`) WITH n LIMIT 5\n"
                "WITH collect(distinct toString(n.`birth_date`)) AS `birth_date_values`\n"
                "RETURN {`birth_date`: {values: `birth_date_values`}} AS output"
            ),
        ),
        (
            "Exhaustive, date time property",
            {"metadata": {"index": []}},
            "Person",
            [{"property": "birth_date", "type": "DATE_TIME"}],
            True,
            5,
            False,
            (
                "MATCH (n:`Person`)\n"
                "WITH min(n.`birth_date`) AS `birth_date_min`,\n"
                "     max(n.`birth_date`) AS `birth_date_max`,\n"
                "     count(distinct n.`birth_date`) AS `birth_date_distinct`\n"
                "RETURN {`birth_date`: {min: toString(`birth_date_min`), max: toString(`birth_date_max`), distinct_count: `birth_date_distinct`}} AS output"
            ),
        ),
        (
            "Non-exhaustive, date time property",
            {"metadata": {"index": []}},
            "Person",
            [{"property": "birth_date", "type": "DATE_TIME"}],
            False,
            5,
            False,
            (
                "MATCH (n:`Person`) WITH n LIMIT 5\n"
                "WITH collect(distinct toString(n.`birth_date`)) AS `birth_date_values`\n"
                "RETURN {`birth_date`: {values: `birth_date_values`}} AS output"
            ),
        ),
        (
            "Exhaustive, local date time property",
            {"metadata": {"index": []}},
            "Person",
            [{"property": "birth_date", "type": "LOCAL_DATE_TIME"}],
            True,
            5,
            False,
            (
                "MATCH (n:`Person`)\n"
                "WITH min(n.`birth_date`) AS `birth_date_min`,\n"
                "     max(n.`birth_date`) AS `birth_date_max`,\n"
                "     count(distinct n.`birth_date`) AS `birth_date_distinct`\n"
                "RETURN {`birth_date`: {min: toString(`birth_date_min`), max: toString(`birth_date_max`), distinct_count: `birth_date_distinct`}} AS output"
            ),
        ),
        (
            "Non-exhaustive, local date time property",
            {"metadata": {"index": []}},
            "Person",
            [{"property": "birth_date", "type": "LOCAL_DATE_TIME"}],
            False,
            5,
            False,
            (
                "MATCH (n:`Person`) WITH n LIMIT 5\n"
                "WITH collect(distinct toString(n.`birth_date`)) AS `birth_date_values`\n"
                "RETURN {`birth_date`: {values: `birth_date_values`}} AS output"
            ),
        ),
        (
            "Exhaustive, list property",
            {"metadata": {"index": []}},
            "Person",
            [{"property": "tags", "type": "LIST"}],
            True,
            5,
            False,
            (
                "MATCH (n:`Person`)\n"
                "WITH min(size(n.`tags`)) AS `tags_size_min`, max(size(n.`tags`)) AS `tags_size_max`\n"
                "RETURN {`tags`: {min_size: `tags_size_min`, max_size: `tags_size_max`}} AS output"
            ),
        ),
        (
            "Non-exhaustive, list property",
            {"metadata": {"index": []}},
            "Person",
            [{"property": "tags", "type": "LIST"}],
            False,
            5,
            False,
            (
                "MATCH (n:`Person`) WITH n LIMIT 5\n"
                "WITH min(size(n.`tags`)) AS `tags_size_min`, max(size(n.`tags`)) AS `tags_size_max`\n"
                "RETURN {`tags`: {min_size: `tags_size_min`, max_size: `tags_size_max`}} AS output"
            ),
        ),
        (
            "Exhaustive, boolean property",
            {"metadata": {"index": []}},
            "Person",
            [{"property": "active", "type": "BOOLEAN"}],
            True,
            5,
            False,
            "MATCH (n:`Person`)\nRETURN {} AS output",
        ),
        (
            "Non-exhaustive, boolean property",
            {"metadata": {"index": []}},
            "Person",
            [{"property": "active", "type": "BOOLEAN"}],
            False,
            5,
            False,
            "MATCH (n:`Person`) WITH n LIMIT 5\nRETURN {} AS output",
        ),
        (
            "Exhaustive, point property",
            {"metadata": {"index": []}},
            "Person",
            [{"property": "location", "type": "POINT"}],
            True,
            5,
            False,
            "MATCH (n:`Person`)\nRETURN {} AS output",
        ),
        (
            "Non-exhaustive, point property",
            {"metadata": {"index": []}},
            "Person",
            [{"property": "location", "type": "POINT"}],
            False,
            5,
            False,
            "MATCH (n:`Person`) WITH n LIMIT 5\nRETURN {} AS output",
        ),
        (
            "Exhaustive, duration property",
            {"metadata": {"index": []}},
            "Journey",
            [{"property": "duration", "type": "DURATION"}],
            True,
            5,
            False,
            ("MATCH (n:`Journey`)\nRETURN {} AS output"),
        ),
        (
            "Non-exhaustive, duration property",
            {"metadata": {"index": []}},
            "Journey",
            [{"property": "duration", "type": "DURATION"}],
            False,
            5,
            False,
            "MATCH (n:`Journey`) WITH n LIMIT 5\nRETURN {} AS output",
        ),
        (
            "Relationship property",
            {"metadata": {"index": []}},
            "FRIENDS_WITH",
            [{"property": "since", "type": "INTEGER"}],
            True,
            5,
            True,
            (
                "MATCH ()-[n:`FRIENDS_WITH`]->()\n"
                "WITH min(n.`since`) AS `since_min`,\n"
                "     max(n.`since`) AS `since_max`,\n"
                "     count(distinct n.`since`) AS `since_distinct`\n"
                "RETURN {`since`: {min: toString(`since_min`), max: toString(`since_max`), distinct_count: `since_distinct`}} AS output"
            ),
        ),
    ],
)
def test_get_enhanced_schema_cypher(
    driver: MagicMock,
    description: str,
    structured_schema: Dict[str, Any],
    label_or_type: str,
    properties: List[Dict[str, Any]],
    exhaustive: bool,
    sample_size: int,
    is_relationship: bool,
    expected_query: str,
) -> None:
    query = get_enhanced_schema_cypher(
        driver=driver,
        structured_schema=structured_schema,
        label_or_type=label_or_type,
        properties=properties,
        exhaustive=exhaustive,
        sample_size=sample_size,
        is_relationship=is_relationship,
    )
    assert query == expected_query, f"Failed test case: {description}"


def test_enhanced_schema_cypher_integer_exhaustive_false_with_index(
    driver: MagicMock,
) -> None:
    structured_schema = {
        "metadata": {
            "index": [
                {
                    "label": "Person",
                    "properties": ["age"],
                    "type": "RANGE",
                }
            ]
        }
    }
    properties = [{"property": "age", "type": "INTEGER"}]
    query = get_enhanced_schema_cypher(
        driver=driver,
        structured_schema=structured_schema,
        label_or_type="Person",
        properties=properties,
        exhaustive=False,
    )
    assert query == (
        "MATCH (n:`Person`) WITH n LIMIT 5\n"
        "WITH min(n.`age`) AS `age_min`,\n"
        "     max(n.`age`) AS `age_max`,\n"
        "     count(distinct n.`age`) AS `age_distinct`\n"
        "RETURN {`age`: {min: toString(`age_min`), max: toString(`age_max`), distinct_count: `age_distinct`}} AS output"
    )


@patch("neo4j_graphrag.schema.query_database")
def test_enhanced_schema_cypher_string_exhaustive_false_with_index(
    query_database_mock: MagicMock,
    driver: MagicMock,
) -> None:
    structured_schema = {
        "metadata": {
            "index": [
                {
                    "label": "Person",
                    "properties": ["status"],
                    "type": "RANGE",
                    "size": 5,
                    "distinctValues": 5,
                }
            ]
        }
    }
    query_database_mock.return_value = [{"value": ["Single", "Married", "Divorced"]}]
    properties = [{"property": "status", "type": "STRING"}]
    query = get_enhanced_schema_cypher(
        driver=driver,
        structured_schema=structured_schema,
        label_or_type="Person",
        properties=properties,
        exhaustive=False,
    )
    assert query == (
        "MATCH (n:`Person`) WITH n LIMIT 5\n\n"
        "RETURN {`status`: {values: ['Single', 'Married', 'Divorced'], distinct_count: 3}} AS output"
    )
